// 版权所有2009年围棋作者。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package ssagen

import (
	"fmt"
	"internal/buildcfg"
	"io/ioutil"
	"log"
	"os"
	"strings"

	"cmd/compile/internal/base"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/staticdata"
	"cmd/compile/internal/typecheck"
	"cmd/compile/internal/types"
	"cmd/internal/obj"
	"cmd/internal/objabi"
)

// SymABIs记录汇编程序提供的有关symbol的信息
// 定义ABI和参考ABI。
type SymABIs struct {
	defs map[string]obj.ABI
	refs map[string]obj.ABISet

	localPrefix string
}

func NewSymABIs(myimportpath string) *SymABIs {
	var localPrefix string
	if myimportpath != "" {
		localPrefix = objabi.PathToPrefix(myimportpath) + "."
	}

	return &SymABIs{
		defs:        make(map[string]obj.ABI),
		refs:        make(map[string]obj.ABISet),
		localPrefix: localPrefix,
	}
}

// canonicalize返回中用于链接器符号的规范名称
// 这是我们的地图。此包中的符号可以写为“.X”或
// 包的导入路径已在符号中。这重写了
// 两者都指向“.”，后者与编译器生成的链接器符号名称相匹配。
func (s *SymABIs) canonicalize(linksym string) string {
	// 如果符号已以localPrefix作为前缀，
	// 重写它以“”开头，使其与
	// 编译器的内部符号名。
	if s.localPrefix != "" && strings.HasPrefix(linksym, s.localPrefix) {
		return `"".` + linksym[len(s.localPrefix):]
	}
	return linksym
}

// ReadSymABIs读取指定定义和定义的symabis文件
// ABI对文本符号的引用。
// None
// symabis格式是一组行，其中每行都是一个序列
// 由空格分隔的字段组成。第一个字段是动词，是
// “def”用于定义符号ABI，或“ref”用于引用符号ABI
// 使用ABI的符号。对于“def”和“ref”，第二个字段为
// 符号名称和第三个字段是ABI名称，作为
// 命名cmd/internal/obj.ABI常量。
func (s *SymABIs) ReadSymABIs(file string) {
	data, err := ioutil.ReadFile(file)
	if err != nil {
		log.Fatalf("-symabis: %v", err)
	}

	for lineNum, line := range strings.Split(string(data), "\n") {
		lineNum++ // 1基
		line = strings.TrimSpace(line)
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}

		parts := strings.Fields(line)
		switch parts[0] {
		case "def", "ref":
			// 解析行。
			if len(parts) != 3 {
				log.Fatalf(`%s:%d: invalid symabi: syntax is "%s sym abi"`, file, lineNum, parts[0])
			}
			sym, abistr := parts[1], parts[2]
			abi, valid := obj.ParseABI(abistr)
			if !valid {
				log.Fatalf(`%s:%d: invalid symabi: unknown abi "%s"`, file, lineNum, abistr)
			}

			sym = s.canonicalize(sym)

			// 以后再录。
			if parts[0] == "def" {
				s.defs[sym] = abi
			} else {
				s.refs[sym] |= obj.ABISetOf(abi)
			}
		default:
			log.Fatalf(`%s:%d: invalid symabi type "%s"`, file, lineNum, parts[0])
		}
	}
}

// GenABIWrappers将ABI信息应用于Funcs并生成ABI
// 必要时使用包装器函数。
func (s *SymABIs) GenABIWrappers() {
	// 对于cgo导出的符号，我们告诉链接器导出
	// 定义ABI到C。这也意味着我们不想
	// 即使存在链接名，也要创建ABI包装。
	// None
	// 托多（奥斯汀）：也许我们想创建ABI包装器，但是
	// 确保链接器在下导出正确的ABI定义
	// 那个不混在一起的名字？
	cgoExports := make(map[string][]*[]string)
	for i, prag := range typecheck.Target.CgoPragmas {
		switch prag[0] {
		case "cgo_export_static", "cgo_export_dynamic":
			symName := s.canonicalize(prag[1])
			pprag := &typecheck.Target.CgoPragmas[i]
			cgoExports[symName] = append(cgoExports[symName], pprag)
		}
	}

	// 将ABI DEF和REF应用于FUNC并生成包装器。
	// None
	// 这可能会为包装器生成新的DECL，但我们
	// 以免我们不想去参观
	// 包装纸的包装纸。
	for _, fn := range typecheck.Target.Decls {
		if fn.Op() != ir.ODCLFUNC {
			continue
		}
		fn := fn.(*ir.Func)
		nam := fn.Nname
		if ir.IsBlank(nam) {
			continue
		}
		sym := nam.Sym()
		var symName string
		if sym.Linkname != "" {
			symName = s.canonicalize(sym.Linkname)
		} else {
			// 这些名称已经是规范名称。
			symName = sym.Pkg.Prefix + "." + sym.Name
		}

		// 应用定义。
		defABI, hasDefABI := s.defs[symName]
		if hasDefABI {
			fn.ABI = defABI
		}

		if fn.Pragma&ir.CgoUnsafeArgs != 0 {
			// CgoUnsafeArgs表示函数（或其被调用方）使用的
			// 当前使用ABI0的分派参数的偏移量
			// 框架布局。将其固定到ABI0。
			fn.ABI = obj.ABI0
		}

		// 如果已导出cgo，请将定义ABI添加到cgo
		// 布拉格马。
		cgoExport := cgoExports[symName]
		for _, pprag := range cgoExport {
			// 导出杂注的形式如下：
			// None
			// cgo_导出*<local>[<remote>]
			// None
			// 如果省略<remote>，则与
			// <local>。
			// None
			// 扩展到
			// None
			// cgo_导出*<local><remote><ABI>
			if len(*pprag) == 2 {
				*pprag = append(*pprag, (*pprag)[1])
			}
			// 添加ABI参数。
			*pprag = append(*pprag, fn.ABI.String())
		}

		// 应用引用。
		if abis, ok := s.refs[symName]; ok {
			fn.ABIRefs |= abis
		}
		// 假设所有函数都至少被引用为
		// ABIInternal，因为它们可以从
		// 其他包。
		fn.ABIRefs.Set(obj.ABIInternal, true)

		// 如果在此包中定义了符号（在
		// Go或assembly）并给定一个链接名，它可能是
		// 从另一个包引用，所以请将其设置为
		// 可通过任何ABI调用。我们知道这一点很重要
		// 它是在这个包中定义的，因为其他包
		// 可能会使用linkname“拉”符号，但我们不希望
		// 创建重复的ABI包装。
		// None
		// 但是，如果为导出到指定了链接名
		// C、 那么我们不做ABI包装，因为cgo
		// 工具需要原始定义。
		hasBody := len(fn.Body) != 0
		if sym.Linkname != "" && (hasBody || hasDefABI) && len(cgoExport) == 0 {
			fn.ABIRefs |= obj.ABISetCallable
		}

		// 再次检查cgo导出的符号是否未获得
		// 任何包装。
		if len(cgoExport) > 0 && fn.ABIRefs&^obj.ABISetOf(fn.ABI) != 0 {
			base.Fatalf("cgo exported function %s cannot have ABI wrappers", fn)
		}

		if !buildcfg.Experiment.RegabiWrappers {
			// 我们将生成ABI别名，而不是
			// 包装器一旦我们在InitLSym中有了lsym。
			continue
		}

		forEachWrapperABI(fn, makeABIWrapper)
	}
}

// InitLSym定义f的obj.LSym并基于
// f。这包括设置符号标志和ABI以及
// 创建和初始化相关的DWARF符号。
// None
// 每个函数必须准确调用InitLSym一次，并且必须
// 既需要有实体的函数，也需要没有实体的函数。
// 对于无体函数，我们只创建LSym；功能
// with Body调用助手来设置/填充LSym。
func InitLSym(f *ir.Func, hasBody bool) {
	if f.LSym != nil {
		base.FatalfAt(f.Pos(), "InitLSym called twice on %v", f)
	}

	if nam := f.Nname; !ir.IsBlank(nam) {
		f.LSym = nam.LinksymABI(f.ABI)
		if f.Pragma&ir.Systemstack != 0 {
			f.LSym.Set(obj.AttrCFunc, true)
		}
		if f.ABI == obj.ABIInternal || !buildcfg.Experiment.RegabiWrappers {
			// 函数值只能指向
			// 内部入口点。这将产生
			// 用于定义
			// 函数或其包装器（视情况而定）。
			// None
			// 如果我们使用ABI别名而不是
			// 包装器，我们只为定义
			// 函数的ABI，因此我们将funcsym
			// 当我们看到这一点。
			staticdata.NeedFuncSym(f)
		}
		if !buildcfg.Experiment.RegabiWrappers {
			// 创建ABI别名而不是包装器。
			forEachWrapperABI(f, makeABIAlias)
		}
	}
	if hasBody {
		setupTextLSym(f, 0)
	}
}

func forEachWrapperABI(fn *ir.Func, cb func(fn *ir.Func, wrapperABI obj.ABI)) {
	need := fn.ABIRefs &^ obj.ABISetOf(fn.ABI)
	if need == 0 {
		return
	}

	for wrapperABI := obj.ABI(0); wrapperABI < obj.ABICount; wrapperABI++ {
		if !need.Get(wrapperABI) {
			continue
		}
		cb(fn, wrapperABI)
	}
}

// makeABIAlias创建一个新的ABI别名，以便通过wrapperABI调用f
// 将由链接器直接解析到f的ABI。
func makeABIAlias(f *ir.Func, wrapperABI obj.ABI) {
	// 这些lsym与本机函数同名，因此
	// 我们直接创建它们，而不是查找它们。
	// f.lsym的唯一性保证了asym的唯一性。
	asym := &obj.LSym{
		Name: f.LSym.Name,
		Type: objabi.SABIALIAS,
		R:    []obj.Reloc{{Sym: f.LSym}}, // 0大小，因此具有“信息性”
	}
	asym.SetABI(wrapperABI)
	asym.Set(obj.AttrDuplicateOK, true)
	base.Ctxt.ABIAliases = append(base.Ctxt.ABIAliases, asym)
}

// makeABIWrapper创建一个新函数，该函数将使用
// wrapperABI并使用f.ABI调用“f”。
func makeABIWrapper(f *ir.Func, wrapperABI obj.ABI) {
	if base.Debug.ABIWrap != 0 {
		fmt.Fprintf(os.Stderr, "=-= %v to %v wrapper for %v\n", wrapperABI, f.ABI, f)
	}

	// 问：需要这样做吗？
	savepos := base.Pos
	savedclcontext := typecheck.DeclContext
	savedcurfn := ir.CurFunc

	base.Pos = base.AutogeneratedPos
	typecheck.DeclContext = ir.PEXTERN

	// 目前我们不支持包装方法，我们需要机器
	// 下面来处理接收器。如果我们看到这种情况，就会感到恐慌。
	ft := f.Nname.Type()
	if ft.NumRecvs() != 0 {
		panic("makeABIWrapper support for wrapping methods not implemented")
	}

	// 制作一个新的func类型以用于包装器。
	var noReceiver *ir.Field
	tfn := ir.NewFuncType(base.Pos,
		noReceiver,
		typecheck.NewFuncParams(ft.Params(), true),
		typecheck.NewFuncParams(ft.Results(), false))

	// 重用f的types.Sym创建一个新的ODCLFUNC/函数。
	fn := typecheck.DeclFunc(f.Nname.Sym(), tfn)
	fn.ABI = wrapperABI

	fn.SetABIWrapper(true)
	fn.SetDupok(true)

	// ABI0到ABIInternal wrappers将主要从加载参数
	// 堆栈到寄存器中（和/或将堆栈位置存储回
	// 在包装调用之后注册）；在大多数情况下，他们不会
	// 需要分配堆栈空间，所以可以标记它们
	// 在这些情况下，这是不可能的。此外，我的假设是
	// 在汇编中编写的函数在大多数（但不是全部）中是不完整的
	// 案例。如果内部目标的数量过多
	// 要将参数装入寄存器，包装器需要
	// 分配堆栈空间，但这似乎不太可能。
	// 因此：请将这些包装纸标记为不开裂。
	// None
	// 另一方面，ABIInternal-to-ABI0包装将采用
	// 寄存器中的对象，并在
	// ABI0调用，这意味着他们将始终需要分配
	// 堆栈空间。如果编译器将它们标记为NOSPLIT，则
	// 好像这会导致链接器
	// nosplit溢出分析将触发链路故障。上
	// 另一方面，如果他们没有标记NOSPLIT，那么这可能导致
	// 构建运行时出现问题（因为可能会调用
	// asm例程（在堆栈增长不安全的情况下）。在里面
	// 大多数情况下，包装器（实际上）是内联的，但是
	// 可能有来自运行时的间接调用可以运行
	// 这里有麻烦了。
	// FIXME：目前我退出时，all.bash不会通过
	// NOSPLIT用于这些包装器，因此所有包装器当前都标记有NOSPLIT。
	fn.Pragma |= ir.Nosplit

	// 生成呼叫。如果没有参数也没有返回，则使用tail调用，
	// 但是一个普通的电话就不行了。
	// None
	// 注意：理想情况下，在以下情况下，我们将使用尾部调用
	// ABI0->ABIInternal wrappers有参数但没有返回，
	// 前提是所有参数都适合寄存器（例如，我们没有
	// 分配任何堆栈空间）。这样做需要一些时间
	// typecheck/walk/ssa中的额外工作可能需要添加新节点
	// OTAILCALL或类似的东西。
	tailcall := tfn.Type().NumResults() == 0 && tfn.Type().NumParams() == 0 && tfn.Type().NumRecvs() == 0
	if base.Ctxt.Arch.Name == "ppc64le" && base.Ctxt.Flag_dynlink {
		// 无法根据需要使用动态链接对PPC64进行跟踪调用
		// 在通话后恢复R2。
		tailcall = false
	}
	if base.Ctxt.Arch.Name == "amd64" && wrapperABI == obj.ABIInternal {
		// 无法根据需要在AMD64上从ABIInternal向ABI0追加调用
		// 返回到ABI内部时，发送到特殊寄存器（X15）。
		tailcall = false
	}

	var tail ir.Node
	if tailcall {
		tail = ir.NewTailCallStmt(base.Pos, f.Nname)
	} else {
		call := ir.NewCallExpr(base.Pos, ir.OCALL, f.Nname, nil)
		call.Args = ir.ParamNames(tfn.Type())
		call.IsDDD = tfn.Type().IsVariadic()
		tail = call
		if tfn.Type().NumResults() > 0 {
			n := ir.NewReturnStmt(base.Pos, nil)
			n.Results = []ir.Node{call}
			tail = n
		}
	}
	fn.Body.Append(tail)

	typecheck.FinishFuncBody()
	if base.Debug.DclStack != 0 {
		types.CheckDclstack()
	}

	typecheck.Func(fn)
	ir.CurFunc = fn
	typecheck.Stmts(fn.Body)

	typecheck.Target.Decls = append(typecheck.Target.Decls, fn)

	// 恢复以前的上下文。
	base.Pos = savepos
	typecheck.DeclContext = savedclcontext
	ir.CurFunc = savedcurfn
}

// setupTextLsym初始化带有正文文字符号的LSym。
func setupTextLSym(f *ir.Func, flag int) {
	if f.Dupok() {
		flag |= obj.DUPOK
	}
	if f.Wrapper() {
		flag |= obj.WRAPPER
	}
	if f.ABIWrapper() {
		flag |= obj.ABIWRAPPER
	}
	if f.Needctxt() {
		flag |= obj.NEEDCTXT
	}
	if f.Pragma&ir.Nosplit != 0 {
		flag |= obj.NOSPLIT
	}
	if f.ReflectMethod() {
		flag |= obj.REFLECTMETHOD
	}

	// 笨拙但重要。
	// 对于可能位于调用延迟
	// 可以恢复的函数（runtime.reflectcall、reflect.callReflect、，
	// 和reflect.callMethod），我们需要恐慌+恢复特殊处理。
	// 有关测试用例和src/reflect/value.go，请参见test/recover.go
	// 对于正在考虑的实际功能。
	// None
	// runtime.reflectcall是一个程序集函数，可调用
	// 包装函数（runtime.callNN）。它的ABI包装器需要包装器
	// 国旗也是。
	fnname := f.Sym().Name
	if base.Ctxt.Pkgpath == "runtime" && fnname == "reflectcall" {
		flag |= obj.WRAPPER
	} else if base.Ctxt.Pkgpath == "reflect" {
		switch fnname {
		case "callReflect", "callMethod":
			flag |= obj.WRAPPER
		}
	}

	base.Ctxt.InitTextSym(f.LSym, flag)
}
